﻿namespace Cassini
{
    using System;
    using System.Globalization;
    using System.Net;
    using System.Net.Sockets;
    using System.Reflection;
    using System.Runtime.Remoting;
    using System.Threading;
    using System.Web;
    using System.Web.Hosting;

    internal class Server : MarshalByRefObject
    {
        private Host _host;
        private string _physicalPath;
        private int _port;
        private bool _shutdownInProgress;
        private Socket _socket;
        private string _virtualPath;

        public Server(int port, string virtualPath, string physicalPath)
        {
            this._port = port;
            this._virtualPath = virtualPath;
            this._physicalPath = physicalPath.EndsWith(@"\", StringComparison.Ordinal) ? physicalPath : (physicalPath + @"\");
        }

        private static Socket CreateSocketBindAndListen(AddressFamily family, IPAddress address, int port)
        {
            Socket socket = new Socket(family, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(new IPEndPoint(address, port));
            socket.Listen(0x7fffffff);
            return socket;
        }

        private static object CreateWorkerAppDomainWithHost(string virtualPath, string physicalPath, Type hostType)
        {
            string appId = (virtualPath + physicalPath).ToLowerInvariant().GetHashCode().ToString("x", CultureInfo.InvariantCulture);
            ApplicationManager applicationManager = ApplicationManager.GetApplicationManager();
            Type type = typeof(HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost");
            IRegisteredObject target = applicationManager.CreateObject(appId, type, virtualPath, physicalPath, false);
            type.InvokeMember("RegisterAssembly", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, target, new object[] { hostType.Assembly.FullName, hostType.Assembly.Location });
            return applicationManager.CreateObject(appId, hostType, virtualPath, physicalPath, false);
        }

        private Host GetHost()
        {
            if (this._shutdownInProgress)
            {
                return null;
            }
            Host host = this._host;
            if (host == null)
            {
                lock (this)
                {
                    host = this._host;
                    if (host == null)
                    {
                        host = (Host) CreateWorkerAppDomainWithHost(this._virtualPath, this._physicalPath, typeof(Host));
                        host.Configure(this, this._port, this._virtualPath, this._physicalPath);
                        this._host = host;
                    }
                }
            }
            return host;
        }

        public void HostStopped()
        {
            this._host = null;
        }

        public override object InitializeLifetimeService()
        {
            return null;
        }

        public void OnRequestEnd(Cassini.Connection conn)
        {
            RemotingServices.Disconnect(conn);
        }

        public void Start()
        {
            try
            {
                this._socket = CreateSocketBindAndListen(AddressFamily.InterNetwork, IPAddress.Loopback, this._port);
            }
            catch
            {
                this._socket = CreateSocketBindAndListen(AddressFamily.InterNetworkV6, IPAddress.IPv6Loopback, this._port);
            }
            ThreadPool.QueueUserWorkItem(delegate {
                while (!this._shutdownInProgress)
                {
                    try
                    {
                        Socket acceptedSocket = this._socket.Accept();
                        ThreadPool.QueueUserWorkItem(delegate {
                            if (!this._shutdownInProgress)
                            {
                                Cassini.Connection conn = new Cassini.Connection(this, acceptedSocket);
                                if (conn.WaitForRequestBytes() == 0)
                                {
                                    conn.WriteErrorAndClose(400);
                                }
                                else
                                {
                                    Host host = this.GetHost();
                                    if (host == null)
                                    {
                                        conn.WriteErrorAndClose(500);
                                    }
                                    else
                                    {
                                        host.ProcessRequest(conn);
                                    }
                                }
                            }
                        });
                        continue;
                    }
                    catch
                    {
                        Thread.Sleep(100);
                        continue;
                    }
                }
            });
        }

        public void Stop()
        {
            this._shutdownInProgress = true;
            try
            {
                if (this._socket != null)
                {
                    this._socket.Close();
                }
            }
            catch
            {
            }
            finally
            {
                this._socket = null;
            }
            try
            {
                if (this._host != null)
                {
                    this._host.Shutdown();
                }
                while (this._host != null)
                {
                    Thread.Sleep(100);
                }
            }
            catch
            {
            }
            finally
            {
                this._host = null;
            }
        }

        public string PhysicalPath
        {
            get
            {
                return this._physicalPath;
            }
        }

        public int Port
        {
            get
            {
                return this._port;
            }
        }

        public string RootUrl
        {
            get
            {
                if (this._port != 80)
                {
                    return ("http://localhost:" + this._port + this._virtualPath);
                }
                return ("http://localhost" + this._virtualPath);
            }
        }

        public string VirtualPath
        {
            get
            {
                return this._virtualPath;
            }
        }
    }
}

